﻿using DrawableObjects.Actors.Aircrafts.Controller;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using DrawableObjects.Actors.Aircrafts;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using GlobalGameClasses;
using System;
using WorldView;
using System.Collections.Generic;
using InterfaceComponents;
using Serialization.Script;
using DrawableObjects.Actors;
using DrawableObjects.Actors.Projectiles;
using Serialization;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Input;
using System.Windows.Forms;
using System.Drawing;

namespace SirianTestEnvironment
{
    /// <summary>
    ///This is a test class for AircraftControllerTest and is intended
    ///to contain all AircraftControllerTest Unit Tests
    ///</summary>
    [TestClass()]
    public class AircraftControllerTest
    {


        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        // 
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        [ClassInitialize()]
        public static void MyClassInitialize(TestContext testContext)
        {
            System.Windows.Forms.Form form = new System.Windows.Forms.Form();
            form.Visible = false;
            form.ShowInTaskbar = false;
            PresentationParameters param = new PresentationParameters();
            param.DeviceWindowHandle = form.Handle;
            param.IsFullScreen = false;
            GraphicsDevice device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.HiDef, param);
            GlobalGameClasses.GlobalGameValues.Values.graphicsDevice = device;
            GlobalGameClasses.GlobalGameValues.Values.spriteBatch = new SpriteBatch(device);
            GlobalGameValues.Values.content.addTexture("smokeTexture", new Texture2D(device, 1, 1));
            GlobalGameValues.Values.content.addTexture("fireTexture", new Texture2D(device, 1, 1));
            GlobalGameValues.Values.content.addTexture("health", new Texture2D(device, 1, 1));
            GlobalGameValues.Values.content.addContent("aircraft_enemy1", null, new Texture2D(device, 1, 1));
            GlobalGameValues.Values.content.addContent("aircraft_boss", null, new Texture2D(device, 1, 1));
            GlobalGameValues.Values.content.addContent("laser_player2", null, new Texture2D(device, 1, 1));
            GlobalGameValues.Values.content.addContent("missile_player2", null, new Texture2D(device, 1, 1));
            GlobalGameValues.Values.content.addContent("boss_energyball", null, new Texture2D(device, 1, 1));
            GlobalGameValues.Values.content.addContent("plasma_enemy", null, new Texture2D(device, 1, 1));
            GlobalGameValues.Values.content.addContent("ring", null, new Texture2D(device, 1, 1));  
            SoundEffect dummySound = new SoundEffect(new byte[8000], 8000, AudioChannels.Mono);
            GlobalGameClasses.GlobalGameValues.Values.content.addSound("missileExplosion", dummySound);
        }
        //
        //Use ClassCleanup to run code after all tests in a class have run
        [ClassCleanup()]
        public static void MyClassCleanup()
        {
            GlobalGameValues.Values.achievementsUnlocked = new List<Achievement>();
            GlobalGameValues.Values.content = new ContentCollection();
            GlobalGameValues.Values.difficulty = null;
            GlobalGameValues.Values.errorWriter = null;
            GlobalGameValues.Values.gameTime = new GameTime();
            GlobalGameValues.Values.GlobalStrings = new StringContainer();
            GlobalGameValues.Values.graphicsDevice = null;
            GlobalGameValues.Values.score = new InterfaceComponents.HUDWrapper<int>(new List<InterfaceComponents.HUDComponent>());
            GlobalGameValues.Values.soundPlayer = null;
            GlobalGameValues.Values.spriteBatch = null;
            GlobalGameValues.Values.volume_Music = 0;
            GlobalGameValues.Values.volume_soundEffect = 0;
        }
        //
        //Use TestInitialize to run code before running each test
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //Use TestCleanup to run code after each test has run
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion


        /// <summary>
        ///A test for setAircraft
        ///</summary>
        [TestMethod()]
        public void setAircraftTest()
        {
            Aircraft aircraft = FabricatableAircraft.GET_ENEMY_1(Matrix.Identity); // TODO: Initialize to an appropriate value
            AircraftController_Accessor target = new AIControlledAircraft_Boss_Accessor(aircraft);
            target.setAircraft(aircraft);
            Assert.AreSame(target.aircraft, aircraft);
        }

        /// <summary>
        ///A test for setAircraft
        ///</summary>
        [TestMethod()]
        public void UpdateTest_AIControlledAircraft_Boss()
        {
            GenericDrawableList drawableList = new GenericDrawableList();
            World myWorld = new World(new AchievementHandler(0, ""), drawableList, new List<InterfaceComponent>(), new LevelScript(new List<ScriptedEvent>()));
            Aircraft aircraft = SpecialEffectsAircraft.GET_BOSS(Matrix.Identity);
            aircraft.world = myWorld;
            aircraft.owner = Owner.ENEMY;
            aircraft.worldBounding = new Sirian.DrawableObject.Actors.Bounding(new Vector2(0f, 0f), 10f, 10f, true);
            Vector2 pointLeft = new Vector2(0, 0);
            Vector2 pointMiddle = new Vector2(50, 0);
            Vector2 pointRight = new Vector2(100, 0);
            AIControlledAircraft_Boss_Accessor controller = new AIControlledAircraft_Boss_Accessor(aircraft);
            controller.directionLeft = pointLeft;
            controller.directionMiddle = pointMiddle;
            controller.directionRight = pointRight;
            controller.setAircraft(aircraft);
            GameTime time = new GameTime(new TimeSpan((long)controller.phaseLength * 10000), new TimeSpan((long)controller.phaseLength * 10000));
            Assert.IsTrue(controller.currentAttack.value__ == AIControlledAircraft_Boss_Accessor.BossAttack.ATTACK_MISSILE.value__);
            Assert.IsTrue(controller.currentMovement.value__ == AIControlledAircraft_Boss_Accessor.BossMovement.MOVE_MIDDLE.value__);
            GlobalGameClasses.GlobalGameValues.Values.gameTime = time;
            controller.Update();
            Assert.IsTrue(controller.currentDestination == pointMiddle);
            Assert.IsTrue(controller.currentAttack.value__ == AIControlledAircraft_Boss_Accessor.BossAttack.ATTACK_LIGHTNING.value__);
            Assert.IsTrue(controller.currentMovement.value__ == AIControlledAircraft_Boss_Accessor.BossMovement.STOP.value__);
            controller.Update();
            Assert.IsTrue(controller.currentAttack.value__ == AIControlledAircraft_Boss_Accessor.BossAttack.ATTACK_LASER.value__);
            Assert.IsTrue(drawableList.getActors(typeof(Projectile))[0] is Laser);
            controller.Update();
            bool moveLeftOrRight = false;
            if (controller.lastMovement.value__ == AIControlledAircraft_Boss_Accessor.BossMovement.MOVE_LEFT.value__)
            {
                moveLeftOrRight = controller.currentDestination == pointLeft;
            }
            else if (controller.lastMovement.value__ == AIControlledAircraft_Boss_Accessor.BossMovement.MOVE_RIGHT.value__)
            {
                moveLeftOrRight = controller.currentDestination == pointRight;
            }
            Assert.IsTrue(moveLeftOrRight);
            Assert.IsTrue(controller.currentAttack.value__ == AIControlledAircraft_Boss_Accessor.BossAttack.ATTACK_MISSILE.value__);
            Assert.IsTrue(controller.currentMovement.value__ == AIControlledAircraft_Boss_Accessor.BossMovement.STOP.value__);
            Assert.IsTrue(drawableList.getActors(typeof(Projectile))[1] is Missile);
            controller.Update();
            Assert.IsTrue(controller.currentAttack.value__ == AIControlledAircraft_Boss_Accessor.BossAttack.ATTACK_LASER.value__);
            Assert.IsTrue(controller.currentMovement.value__ == AIControlledAircraft_Boss_Accessor.BossMovement.MOVE_MIDDLE.value__);
            GlobalGameClasses.GlobalGameValues.Values.gameTime = new GameTime(new TimeSpan((long)controller.countdown * 100000), new TimeSpan((long)controller.countdown * 100000));
            controller.Update();
            controller.Update();
            Assert.IsTrue(controller.currentMovement.value__ == AIControlledAircraft_Boss_Accessor.BossMovement.STOP.value__);
        }

        [TestMethod()]
        public void UpdateTest_PlayerControlledAircraft()
        {
            GenericDrawableList drawableList = new GenericDrawableList();
            World myWorld = new World(new AchievementHandler(0, ""), drawableList, new List<InterfaceComponent>(), new LevelScript(new List<ScriptedEvent>()));
            Aircraft aircraft = SpecialEffectsAircraft.GET_BOSS(Matrix.Identity);
            PlayerControlledAircraft_Accessor controller = new PlayerControlledAircraft_Accessor(aircraft);
            aircraft.world = myWorld;
            aircraft.worldBounding = new Sirian.DrawableObject.Actors.Bounding(new Vector2(-100f, -100f), 200f, 200f, true);
            aircraft.owner = Owner.ENEMY;

            Vector2 currentPosition = aircraft.getGamePosition();
            controller.Update(new KeyboardState(new Microsoft.Xna.Framework.Input.Keys[]{AircraftCommand.MOVE_FORWARD.standardKey}));
            GlobalGameClasses.GlobalGameValues.Values.gameTime = new GameTime(new TimeSpan(100), new TimeSpan(100));
            aircraft.Update();
            float delta = currentPosition.Y - aircraft.getGamePosition().Y;
            Assert.IsTrue(Math.Abs(delta - GlobalGameClasses.GlobalGameValues.SPEEDFACTOR * aircraft.speed * 0.01) <= 0.0001f);
            Assert.IsTrue(Math.Abs(currentPosition.X - aircraft.getGamePosition().X) <= 0.0001f);

            currentPosition = aircraft.getGamePosition();
            controller.Update(new KeyboardState(new Microsoft.Xna.Framework.Input.Keys[] { AircraftCommand.MOVE_BACK.standardKey }));
            GlobalGameClasses.GlobalGameValues.Values.gameTime = new GameTime(new TimeSpan(100), new TimeSpan(100));
            aircraft.Update();
            delta = - currentPosition.Y + aircraft.getGamePosition().Y;
            Assert.IsTrue(Math.Abs(delta - GlobalGameClasses.GlobalGameValues.SPEEDFACTOR * aircraft.speed * 0.01) <= 0.0001f);
            Assert.IsTrue(Math.Abs(currentPosition.X - aircraft.getGamePosition().X) <= 0.0001f);

            currentPosition = aircraft.getGamePosition();
            controller.Update(new KeyboardState(new Microsoft.Xna.Framework.Input.Keys[] { AircraftCommand.MOVE_LEFT.standardKey }));
            GlobalGameClasses.GlobalGameValues.Values.gameTime = new GameTime(new TimeSpan(100), new TimeSpan(100));
            aircraft.Update();
            delta = currentPosition.X - aircraft.getGamePosition().X;
            Assert.IsTrue(Math.Abs(delta - GlobalGameClasses.GlobalGameValues.SPEEDFACTOR * aircraft.speed * 0.01) <= 0.0001f);
            Assert.IsTrue(Math.Abs(currentPosition.Y - aircraft.getGamePosition().Y) <= 0.0001f);

            currentPosition = aircraft.getGamePosition();
            controller.Update(new KeyboardState(new Microsoft.Xna.Framework.Input.Keys[] { AircraftCommand.MOVE_RIGHT.standardKey }));
            GlobalGameClasses.GlobalGameValues.Values.gameTime = new GameTime(new TimeSpan(100), new TimeSpan(100));
            aircraft.Update();
            delta = - currentPosition.X + aircraft.getGamePosition().X;
            Assert.IsTrue(Math.Abs(delta - GlobalGameClasses.GlobalGameValues.SPEEDFACTOR * aircraft.speed * 0.01) <= 0.0001f);
            Assert.IsTrue(Math.Abs(currentPosition.Y - aircraft.getGamePosition().Y) <= 0.0001f);

            controller.Update(new KeyboardState(new Microsoft.Xna.Framework.Input.Keys[] {AircraftCommand.FIRE.standardKey }));
            aircraft.Update();
            Assert.IsTrue(myWorld.getActors(typeof(Projectile), Owner.ENEMY).Count > 0);
        }

        [TestMethod()]
        public void UpdateTest_AIControlledAircraft_PathGuided_FlightTest()
        {
            GenericDrawableList drawableList = new GenericDrawableList();
            World myWorld = new World(new AchievementHandler(0, ""), drawableList, new List<InterfaceComponent>(), new LevelScript(new List<ScriptedEvent>()));
            Aircraft aircraft = SpecialEffectsAircraft.GET_BOSS(Matrix.Identity);
            List<Vector2> pathPoints = new List<Vector2>();
            pathPoints.Add(new Vector2(0, 1));
            pathPoints.Add(new Vector2(100, 30));
            pathPoints.Add(new Vector2(0, 60));
            pathPoints.Add(new Vector2(-100, 90));
            pathPoints.Add(new Vector2(-100, 90));

            Path flightPath = new Path(pathPoints);
            AIControlledAircraft_PathGuided_Accessor controller = new AIControlledAircraft_PathGuided_Accessor(flightPath);
            controller.aircraft = aircraft;
            aircraft.world = myWorld;
            aircraft.worldBounding = new Sirian.DrawableObject.Actors.Bounding(new Vector2(-100f, -100f), 200f, 200f, false);
            aircraft.owner = Owner.ENEMY;

            int updateRate = 166000;    //~ 60Hz
            GlobalGameClasses.GlobalGameValues.Values.gameTime = new GameTime(new TimeSpan(updateRate), new TimeSpan(updateRate)); 
            List<Vector2> flightPoints = new List<Vector2>();
            int max = 100000;
            while (!flightPath.isFinished() && max > 0)
            {
                controller.Update();
                aircraft.Update();
                flightPoints.Add(aircraft.getGamePosition());
                //aircraft.setPosition(aircraft.getGamePosition() + new Vector2(0, 0.6f)); // Gravity
                max--;
            }

            showAndDraw(flightPoints, pathPoints);

            Vector2 vector1;
            Vector2 vector2;
            double angle;
            double maxValue = (1d / 4d) * Math.PI;
            double minValue = (3d / 4d) * Math.PI;
            for (int i = 0; i < flightPoints.Count - 2; i++)
            {
                vector1 = flightPoints[i] - flightPoints[i + 1];
                vector2 = flightPoints[i + 1] - flightPoints[i + 2];
                vector1.Normalize();
                vector2.Normalize();
                angle = Math.Acos(Vector2.Dot(vector1, vector2));
                if (Double.IsNaN(angle)) angle = 0d;
                Assert.IsTrue(Math.Abs(angle) < maxValue || Math.Abs(angle) > minValue);
            }
        }

        [TestMethod()]
        public void UpdateTest_AIControlledAircraft_PathGuided_WeaponTest()
        {
            GenericDrawableList drawableList = new GenericDrawableList();
            World myWorld = new World(new AchievementHandler(0, ""), drawableList, new List<InterfaceComponent>(), new LevelScript(new List<ScriptedEvent>()));
            Aircraft aircraft = FabricatableAircraft.GET_ENEMY_1(Matrix.Identity);
            List<Vector2> pathPoints = new List<Vector2>();
            Path flightPath = new Path(pathPoints);
            AIControlledAircraft_PathGuided_Accessor controller = new AIControlledAircraft_PathGuided_Accessor(flightPath);
            controller.aircraft = aircraft;
            aircraft.world = myWorld;
            aircraft.setPosition(new Vector2(0, 0));
            aircraft.worldBounding = new Sirian.DrawableObject.Actors.Bounding(new Vector2(-100f, -100f), 200f, 200f, true);
            aircraft.owner = Owner.ENEMY;

            Aircraft targetDummy = SpecialEffectsAircraft.GET_BOSS(Matrix.Identity);
            targetDummy.world = myWorld;
            targetDummy.worldBounding = new Sirian.DrawableObject.Actors.Bounding(new Vector2(-100f, -100f), 200f, 200f, true);
            targetDummy.setPosition(new Vector2(100,0));
            targetDummy.owner = Owner.PLAYER;
            myWorld.addActor(targetDummy);

            int updateRate = 1000000;    
            GlobalGameClasses.GlobalGameValues.Values.gameTime = new GameTime(new TimeSpan(updateRate), new TimeSpan(updateRate));
            controller.Update();
            Assert.IsTrue(myWorld.getActors(typeof(Projectile), Owner.ENEMY).Count == 1);
            Projectile projectile = (Projectile)myWorld.getActors(typeof(Projectile), Owner.ENEMY)[0];
            Vector2 startposition = projectile.getGamePosition();
            projectile.Update();
            projectile.Update();
            Vector2 flightVector = projectile.getGamePosition() - startposition;
            Vector2 targetVector = targetDummy.getGamePosition() - startposition;
            flightVector.Normalize();
            targetVector.Normalize();
            float dot = Vector2.Dot(flightVector, targetVector);
            Assert.IsTrue(dot >= 0.9);
        }

        private void showAndDraw(List<Vector2> flightPoints, List<Vector2> pathPoints)
        {
            Form testForm = new Form();
            testForm.Width = 600;
            testForm.Height = 800;
            PictureBox pictureBox = new PictureBox();
            pictureBox.Width = 600;
            pictureBox.Height = 800;
            pictureBox.Left = 0;
            pictureBox.Top = 0;
            testForm.Controls.Add(pictureBox);
            Bitmap DrawArea = new Bitmap(pictureBox.Size.Width, pictureBox.Size.Height);
            Graphics g = Graphics.FromImage(DrawArea);
            Pen mypen = new Pen(Brushes.Black);

            for (int i = 0; i < flightPoints.Count - 1; i++)
            {
                g.DrawLine(mypen, new PointF(flightPoints[i].X * 2 + 200, flightPoints[i].Y * 2 + 200), new PointF(flightPoints[i + 1].X * 2 + 200, flightPoints[i + 1].Y * 2 + 200));
            }
            mypen = new Pen(Brushes.Red);
            Font font = new Font(FontFamily.GenericSerif, 15.0f);
            Brush myBrush = Brushes.Red;
            int j = 0; 
            foreach (Vector2 pathPoint in pathPoints)
            {
                g.DrawString(j.ToString(), font, myBrush, new PointF(pathPoint.X * 2 + 205, pathPoint.Y * 2 + 205));
                g.DrawPie(mypen, new RectangleF(new PointF(pathPoint.X * 2 + 195, pathPoint.Y * 2 + 195), new SizeF(10, 10)), 0, 360);
                j++;
            }
            pictureBox.Image = DrawArea;
            g.Dispose();

            testForm.ShowDialog();
        }
    }
}
